/*-------------------------------------------------------------------------
 *
 * pg_walinspect.c
 *		  检查 PostgreSQL 预写日志内容的功能
 *
 * Copyright (c) 2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		  contrib/pg_walinspect/pg_walinspect.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogreader.h"
#include "access/xlogrecovery.h"
#include "access/xlogstats.h"
#include "access/xlogutils.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"

/*
 * 注意：对于这里的任何代码更改或问题修复，强烈建议考虑
 * 在pg_waldump工具中进行相同的操作。
 */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(pg_get_wal_record_info);
PG_FUNCTION_INFO_V1(pg_get_wal_records_info);
PG_FUNCTION_INFO_V1(pg_get_wal_records_info_till_end_of_wal);
PG_FUNCTION_INFO_V1(pg_get_wal_stats);
PG_FUNCTION_INFO_V1(pg_get_wal_stats_till_end_of_wal);

static bool fc_IsFutureLSN(XLogRecPtr fc_lsn, XLogRecPtr *fc_curr_lsn);
static XLogReaderState *fc_InitXLogReaderState(XLogRecPtr fc_lsn);
static XLogRecord *fc_ReadNextXLogRecord(XLogReaderState *fc_xlogreader);
static void fc_GetWALRecordInfo(XLogReaderState *fc_record, Datum *fc_values,
							 bool *fc_nulls, uint32 fc_ncols);
static XLogRecPtr fc_ValidateInputLSNs(bool fc_till_end_of_wal,
									XLogRecPtr fc_start_lsn, XLogRecPtr fc_end_lsn);
static void fc_GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr fc_start_lsn,
							  XLogRecPtr fc_end_lsn);
static void fc_GetXLogSummaryStats(XLogStats *fc_stats, ReturnSetInfo *fc_rsinfo,
								Datum *fc_values, bool *fc_nulls, uint32 fc_ncols,
								bool fc_stats_per_record);
static void fc_FillXLogStatsRow(const char *fc_name, uint64 fc_n, uint64 fc_total_count,
							 uint64 fc_rec_len, uint64 fc_total_rec_len,
							 uint64 fc_fpi_len, uint64 fc_total_fpi_len,
							 uint64 fc_tot_len, uint64 fc_total_len,
							 Datum *fc_values, bool *fc_nulls, uint32 fc_ncols);
static void fc_GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr fc_start_lsn,
						XLogRecPtr fc_end_lsn, bool fc_stats_per_record);

/*
 * 检查给定的LSN是否在未来。同时，返回服务器拥有的WAL的LSN。
 */
static bool fc_IsFutureLSN(XLogRecPtr fc_lsn, XLogRecPtr *fc_curr_lsn)
{
	/*
	 * 我们确定服务器的当前LSN，方式类似于page_read
	 * callback read_local_xlog_page_no_wait的方式。
	 */
	if (!RecoveryInProgress())
		*fc_curr_lsn = GetFlushRecPtr(NULL);
	else
		*fc_curr_lsn = GetXLogReplayRecPtr(NULL);

	Assert(!XLogRecPtrIsInvalid(*fc_curr_lsn));

	if (fc_lsn >= *fc_curr_lsn)
		return true;

	return false;
}

/*
 * 初始化WAL读取器并确定第一个有效LSN。
 */
static XLogReaderState *
fc_InitXLogReaderState(XLogRecPtr fc_lsn)
{
	XLogReaderState *fc_xlogreader;
	ReadLocalXLogPageNoWaitPrivate *fc_private_data;
	XLogRecPtr	fc_first_valid_record;

	/*
	 * 不允许读取第一个段的第一页以下的WAL。
	 * 这是一个引导WAL页面，page_read回调无法读取它。
	 */
	if (fc_lsn < XLOG_BLCKSZ)
		ereport(ERROR,
				(errmsg("could not read WAL at LSN %X/%X",
						LSN_FORMAT_ARGS(fc_lsn))));

	fc_private_data = (ReadLocalXLogPageNoWaitPrivate *)
		palloc0(sizeof(ReadLocalXLogPageNoWaitPrivate));

	fc_xlogreader = XLogReaderAllocate(wal_segment_size, NULL,
									XL_ROUTINE(.page_read = &read_local_xlog_page_no_wait,
											   .segment_open = &wal_segment_open,
											   .segment_close = &wal_segment_close),
									fc_private_data);

	if (fc_xlogreader == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory"),
				 errdetail("Failed while allocating a WAL reading processor.")));

	/* 首先找到一个有效的 recptr 作为起点 */
	fc_first_valid_record = XLogFindNextRecord(fc_xlogreader, fc_lsn);

	if (XLogRecPtrIsInvalid(fc_first_valid_record))
		ereport(ERROR,
				(errmsg("could not find a valid record after %X/%X",
						LSN_FORMAT_ARGS(fc_lsn))));

	return fc_xlogreader;
}

/*
 * 读取下一个WAL记录。
 *
 * 根据设计，为了减少对运行系统的干扰，未分配插槽
 * 以保留我们即将读取的WAL。因此，函数可能会遇到
 * 历史WAL的读取错误。
 *
 * 我们通过将end_lsn限制为已刷新WAL来防止尝试读取尚未
 * 写入的WAL时发生普通错误，但如果刷新指针落在记录中间
 * 也可能会遇到错误。在这种情况下，我们将返回NULL。
 */
static XLogRecord * fc_ReadNextXLogRecord(XLogReaderState *fc_xlogreader)
{
	XLogRecord *fc_record;
	char	   *fc_errormsg;

	fc_record = XLogReadRecord(fc_xlogreader, &fc_errormsg);

	if (fc_record == NULL)
	{
		ReadLocalXLogPageNoWaitPrivate *fc_private_data;

		/* 如果达到WAL末尾则返回NULL */
		fc_private_data = (ReadLocalXLogPageNoWaitPrivate *)
			fc_xlogreader->private_data;

		if (fc_private_data->end_of_wal)
			return NULL;

		if (fc_errormsg)
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read WAL at %X/%X: %s",
							LSN_FORMAT_ARGS(fc_xlogreader->EndRecPtr), fc_errormsg)));
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not read WAL at %X/%X",
							LSN_FORMAT_ARGS(fc_xlogreader->EndRecPtr))));
	}

	return fc_record;
}

/*
 * 获取单个WAL记录信息。
 */
static void fc_GetWALRecordInfo(XLogReaderState *fc_record, Datum *fc_values,
				 bool *fc_nulls, uint32 fc_ncols)
{
	const char *fc_id;
	RmgrData	fc_desc;
	uint32		fc_fpi_len = 0;
	StringInfoData fc_rec_desc;
	StringInfoData fc_rec_blk_ref;
	uint32		fc_main_data_len;
	int			fc_i = 0;

	fc_desc = GetRmgr(XLogRecGetRmid(fc_record));
	fc_id = fc_desc.rm_identify(XLogRecGetInfo(fc_record));

	if (fc_id == NULL)
		fc_id = psprintf("UNKNOWN (%x)", XLogRecGetInfo(fc_record) & ~XLR_INFO_MASK);

	initStringInfo(&fc_rec_desc);
	fc_desc.rm_desc(&fc_rec_desc, fc_record);

	/* 块引用。 */
	initStringInfo(&fc_rec_blk_ref);
	XLogRecGetBlockRefInfo(fc_record, false, true, &fc_rec_blk_ref, &fc_fpi_len);

	fc_main_data_len = XLogRecGetDataLen(fc_record);

	fc_values[fc_i++] = LSNGetDatum(fc_record->ReadRecPtr);
	fc_values[fc_i++] = LSNGetDatum(fc_record->EndRecPtr);
	fc_values[fc_i++] = LSNGetDatum(XLogRecGetPrev(fc_record));
	fc_values[fc_i++] = TransactionIdGetDatum(XLogRecGetXid(fc_record));
	fc_values[fc_i++] = CStringGetTextDatum(fc_desc.rm_name);
	fc_values[fc_i++] = CStringGetTextDatum(fc_id);
	fc_values[fc_i++] = UInt32GetDatum(XLogRecGetTotalLen(fc_record));
	fc_values[fc_i++] = UInt32GetDatum(fc_main_data_len);
	fc_values[fc_i++] = UInt32GetDatum(fc_fpi_len);
	fc_values[fc_i++] = CStringGetTextDatum(fc_rec_desc.data);
	fc_values[fc_i++] = CStringGetTextDatum(fc_rec_blk_ref.data);

	Assert(fc_i == fc_ncols);
}

/*
 * 获取WAL记录信息。
 *
 * 如果指定了未来的WAL LSN，即数据库系统不知道的WAL LSN，
 * 此函数将发出错误。
 */
Datum pg_get_wal_record_info(PG_FUNCTION_ARGS)
{
#define PG_GET_WAL_RECORD_INFO_COLS 11
	Datum		fc_result;
	Datum		fc_values[PG_GET_WAL_RECORD_INFO_COLS];
	bool		fc_nulls[PG_GET_WAL_RECORD_INFO_COLS];
	XLogRecPtr	fc_lsn;
	XLogRecPtr	fc_curr_lsn;
	XLogReaderState *fc_xlogreader;
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_tuple;

	fc_lsn = PG_GETARG_LSN(0);

	if (fc_IsFutureLSN(fc_lsn, &fc_curr_lsn))
	{
		/*
		 * GetFlushRecPtr或GetXLogReplayRecPtr分别提供上一个
		 * 刷新或重放记录的“end+1” LSN。但在用户面对的消息中
		 * 我们使用“end”对应的LSN。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("cannot accept future input LSN"),
				 errdetail("Last known WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(fc_curr_lsn))));
	}

	/* 为我们的结果类型构建元组描述符。 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	fc_xlogreader = fc_InitXLogReaderState(fc_lsn);

	if (!fc_ReadNextXLogRecord(fc_xlogreader))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("could not read WAL at %X/%X",
						LSN_FORMAT_ARGS(fc_xlogreader->EndRecPtr))));

	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	fc_GetWALRecordInfo(fc_xlogreader, fc_values, fc_nulls, PG_GET_WAL_RECORD_INFO_COLS);

	pfree(fc_xlogreader->private_data);
	XLogReaderFree(fc_xlogreader);

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
	fc_result = HeapTupleGetDatum(fc_tuple);

	PG_RETURN_DATUM(fc_result);
#undef PG_GET_WAL_RECORD_INFO_COLS
}

/*
 * 验证输入LSN并计算直到end_of_wal版本的end LSN。
 */
static XLogRecPtr fc_ValidateInputLSNs(bool fc_till_end_of_wal, XLogRecPtr fc_start_lsn,
				  XLogRecPtr fc_end_lsn)
{
	XLogRecPtr	fc_curr_lsn;

	if (fc_IsFutureLSN(fc_start_lsn, &fc_curr_lsn))
	{
		/*
		 * GetFlushRecPtr或GetXLogReplayRecPtr分别提供上一个
		 * 刷新或重放记录的“end+1” LSN。但在用户面对的消息中
		 * 我们使用“end”对应的LSN。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("cannot accept future start LSN"),
				 errdetail("Last known WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(fc_curr_lsn))));
	}

	if (fc_till_end_of_wal)
		fc_end_lsn = fc_curr_lsn;

	if (fc_end_lsn > fc_curr_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("cannot accept future end LSN"),
				 errdetail("Last known WAL LSN on the database system is at %X/%X.",
						   LSN_FORMAT_ARGS(fc_curr_lsn))));

	if (fc_start_lsn >= fc_end_lsn)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("WAL start LSN must be less than end LSN")));

	return fc_end_lsn;
}

/*
 * 获取开始LSN和结束LSN之间的所有WAL记录的信息和数据。
 */
static void fc_GetWALRecordsInfo(FunctionCallInfo fcinfo, XLogRecPtr fc_start_lsn,
				  XLogRecPtr fc_end_lsn)
{
#define PG_GET_WAL_RECORDS_INFO_COLS 11
	XLogReaderState *fc_xlogreader;
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Datum		fc_values[PG_GET_WAL_RECORDS_INFO_COLS];
	bool		fc_nulls[PG_GET_WAL_RECORDS_INFO_COLS];
	MemoryContext fc_old_cxt;
	MemoryContext fc_tmp_cxt;

	InitMaterializedSRF(fcinfo, 0);

	fc_xlogreader = fc_InitXLogReaderState(fc_start_lsn);

	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	fc_tmp_cxt = AllocSetContextCreate(CurrentMemoryContext,
									"GetWALRecordsInfo temporary cxt",
									ALLOCSET_DEFAULT_SIZES);

	while (fc_ReadNextXLogRecord(fc_xlogreader) &&
		   fc_xlogreader->EndRecPtr <= fc_end_lsn)
	{
		/* 使用临时上下文，以便在每个元组处理完成后进行清理 */
		fc_old_cxt = MemoryContextSwitchTo(fc_tmp_cxt);

		fc_GetWALRecordInfo(fc_xlogreader, fc_values, fc_nulls,
						 PG_GET_WAL_RECORDS_INFO_COLS);

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);

		/* 清理并切换回去 */
		MemoryContextSwitchTo(fc_old_cxt);
		MemoryContextReset(fc_tmp_cxt);

		CHECK_FOR_INTERRUPTS();
	}

	MemoryContextDelete(fc_tmp_cxt);
	pfree(fc_xlogreader->private_data);
	XLogReaderFree(fc_xlogreader);

#undef PG_GET_WAL_RECORDS_INFO_COLS
}

/*
 * 获取开始LSN和结束LSN之间的所有WAL记录的信息和数据。
 *
 * 如果指定了未来的开始或结束WAL LSN，即数据库系统不知道的
 * WAL LSN，此函数将发出错误。
 */
Datum pg_get_wal_records_info(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_start_lsn;
	XLogRecPtr	fc_end_lsn;

	fc_start_lsn = PG_GETARG_LSN(0);
	fc_end_lsn = PG_GETARG_LSN(1);

	fc_end_lsn = fc_ValidateInputLSNs(false, fc_start_lsn, fc_end_lsn);

	fc_GetWALRecordsInfo(fcinfo, fc_start_lsn, fc_end_lsn);

	PG_RETURN_VOID();
}

/*
 * 获取从开始LSN到WAL末尾的所有WAL记录的信息和数据。
 *
 * 如果指定了未来的开始WAL LSN，即数据库系统不知道的WAL LSN，
 * 此函数将发出错误。
 */
Datum pg_get_wal_records_info_till_end_of_wal(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_start_lsn;
	XLogRecPtr	fc_end_lsn = InvalidXLogRecPtr;

	fc_start_lsn = PG_GETARG_LSN(0);

	fc_end_lsn = fc_ValidateInputLSNs(true, fc_start_lsn, fc_end_lsn);

	fc_GetWALRecordsInfo(fcinfo, fc_start_lsn, fc_end_lsn);

	PG_RETURN_VOID();
}

/*
 * 填充rmgr或记录的记录计数和大小的单行。
 */
static void fc_FillXLogStatsRow(const char *fc_name,
				 uint64 fc_n, uint64 fc_total_count,
				 uint64 fc_rec_len, uint64 fc_total_rec_len,
				 uint64 fc_fpi_len, uint64 fc_total_fpi_len,
				 uint64 fc_tot_len, uint64 fc_total_len,
				 Datum *fc_values, bool *fc_nulls, uint32 fc_ncols)
{
	double		fc_n_pct,
				fc_rec_len_pct,
				fc_fpi_len_pct,
				fc_tot_len_pct;
	int			fc_i = 0;

	fc_n_pct = 0;
	if (fc_total_count != 0)
		fc_n_pct = 100 * (double) fc_n / fc_total_count;

	fc_rec_len_pct = 0;
	if (fc_total_rec_len != 0)
		fc_rec_len_pct = 100 * (double) fc_rec_len / fc_total_rec_len;

	fc_fpi_len_pct = 0;
	if (fc_total_fpi_len != 0)
		fc_fpi_len_pct = 100 * (double) fc_fpi_len / fc_total_fpi_len;

	fc_tot_len_pct = 0;
	if (fc_total_len != 0)
		fc_tot_len_pct = 100 * (double) fc_tot_len / fc_total_len;

	fc_values[fc_i++] = CStringGetTextDatum(fc_name);
	fc_values[fc_i++] = Int64GetDatum(fc_n);
	fc_values[fc_i++] = Float8GetDatum(fc_n_pct);
	fc_values[fc_i++] = Int64GetDatum(fc_rec_len);
	fc_values[fc_i++] = Float8GetDatum(fc_rec_len_pct);
	fc_values[fc_i++] = Int64GetDatum(fc_fpi_len);
	fc_values[fc_i++] = Float8GetDatum(fc_fpi_len_pct);
	fc_values[fc_i++] = Int64GetDatum(fc_tot_len);
	fc_values[fc_i++] = Float8GetDatum(fc_tot_len_pct);

	Assert(fc_i == fc_ncols);
}

/*
 * 获取到目前为止看到的记录的汇总统计信息。
 */
static void fc_GetXLogSummaryStats(XLogStats *fc_stats, ReturnSetInfo *fc_rsinfo,
					Datum *fc_values, bool *fc_nulls, uint32 fc_ncols,
					bool fc_stats_per_record)
{
	uint64		fc_total_count = 0;
	uint64		fc_total_rec_len = 0;
	uint64		fc_total_fpi_len = 0;
	uint64		fc_total_len = 0;
	int			fc_ri;

	/*
	 * 每一行显示其相对于总数的百分比，因此首先要计算列总和。
	 */
	for (fc_ri = 0; fc_ri <= RM_MAX_ID; fc_ri++)
	{
		if (!RmgrIdIsValid(fc_ri))
			continue;

		fc_total_count += fc_stats->rmgr_stats[fc_ri].count;
		fc_total_rec_len += fc_stats->rmgr_stats[fc_ri].rec_len;
		fc_total_fpi_len += fc_stats->rmgr_stats[fc_ri].fpi_len;
	}
	fc_total_len = fc_total_rec_len + fc_total_fpi_len;

	for (fc_ri = 0; fc_ri <= RM_MAX_ID; fc_ri++)
	{
		uint64		fc_count;
		uint64		fc_rec_len;
		uint64		fc_fpi_len;
		uint64		fc_tot_len;
		RmgrData	fc_desc;

		if (!RmgrIdIsValid(fc_ri))
			continue;

		if (!RmgrIdExists(fc_ri))
			continue;

		fc_desc = GetRmgr(fc_ri);

		if (fc_stats_per_record)
		{
			int			fc_rj;

			for (fc_rj = 0; fc_rj < MAX_XLINFO_TYPES; fc_rj++)
			{
				const char *fc_id;

				fc_count = fc_stats->record_stats[fc_ri][fc_rj].count;
				fc_rec_len = fc_stats->record_stats[fc_ri][fc_rj].rec_len;
				fc_fpi_len = fc_stats->record_stats[fc_ri][fc_rj].fpi_len;
				fc_tot_len = fc_rec_len + fc_fpi_len;

				/* 跳过未定义的组合和未发生的组合 */
				if (fc_count == 0)
					continue;

				/* xl_info 中的上四位是 rmgr 的 */
				fc_id = fc_desc.rm_identify(fc_rj << 4);
				if (fc_id == NULL)
					fc_id = psprintf("UNKNOWN (%x)", fc_rj << 4);

				fc_FillXLogStatsRow(psprintf("%s/%s", fc_desc.rm_name, fc_id), fc_count,
								 fc_total_count, fc_rec_len, fc_total_rec_len, fc_fpi_len,
								 fc_total_fpi_len, fc_tot_len, fc_total_len,
								 fc_values, fc_nulls, fc_ncols);

				tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
									 fc_values, fc_nulls);
			}
		}
		else
		{
			fc_count = fc_stats->rmgr_stats[fc_ri].count;
			fc_rec_len = fc_stats->rmgr_stats[fc_ri].rec_len;
			fc_fpi_len = fc_stats->rmgr_stats[fc_ri].fpi_len;
			fc_tot_len = fc_rec_len + fc_fpi_len;

			fc_FillXLogStatsRow(fc_desc.rm_name, fc_count, fc_total_count, fc_rec_len,
							 fc_total_rec_len, fc_fpi_len, fc_total_fpi_len, fc_tot_len,
							 fc_total_len, fc_values, fc_nulls, fc_ncols);

			tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
								 fc_values, fc_nulls);
		}
	}
}

/*
 * 获取起始 LSN 和结束 LSN 之间的 WAL 统计信息。
 */
static void fc_GetWalStats(FunctionCallInfo fcinfo, XLogRecPtr fc_start_lsn,
			XLogRecPtr fc_end_lsn, bool fc_stats_per_record)
{
#define PG_GET_WAL_STATS_COLS 9
	XLogReaderState *fc_xlogreader;
	XLogStats	fc_stats;
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Datum		fc_values[PG_GET_WAL_STATS_COLS];
	bool		fc_nulls[PG_GET_WAL_STATS_COLS];

	InitMaterializedSRF(fcinfo, 0);

	fc_xlogreader = fc_InitXLogReaderState(fc_start_lsn);

	MemSet(&fc_stats, 0, sizeof(fc_stats));

	while (fc_ReadNextXLogRecord(fc_xlogreader) &&
		   fc_xlogreader->EndRecPtr <= fc_end_lsn)
	{
		XLogRecStoreStats(&fc_stats, fc_xlogreader);

		CHECK_FOR_INTERRUPTS();
	}

	pfree(fc_xlogreader->private_data);
	XLogReaderFree(fc_xlogreader);

	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	fc_GetXLogSummaryStats(&fc_stats, fc_rsinfo, fc_values, fc_nulls,
						PG_GET_WAL_STATS_COLS,
						fc_stats_per_record);

#undef PG_GET_WAL_STATS_COLS
}

/*
 * 获取起始 LSN 和结束 LSN 之间所有 WAL 记录的统计信息。
 *
 * 如果指定了未来的起始或结束 WAL LSN，即数据库系统不知道的 WAL LSN，
 * 该函数将发出错误。
 */
Datum pg_get_wal_stats(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_start_lsn;
	XLogRecPtr	fc_end_lsn;
	bool		fc_stats_per_record;

	fc_start_lsn = PG_GETARG_LSN(0);
	fc_end_lsn = PG_GETARG_LSN(1);
	fc_stats_per_record = PG_GETARG_BOOL(2);

	fc_end_lsn = fc_ValidateInputLSNs(false, fc_start_lsn, fc_end_lsn);

	fc_GetWalStats(fcinfo, fc_start_lsn, fc_end_lsn, fc_stats_per_record);

	PG_RETURN_VOID();
}

/*
 * 获取从起始 LSN 到 WAL 末尾的所有 WAL 记录的统计信息。
 *
 * 如果指定了未来的起始 WAL LSN，即数据库系统不知道的 WAL LSN，
 * 该函数将发出错误。
 */
Datum pg_get_wal_stats_till_end_of_wal(PG_FUNCTION_ARGS)
{
	XLogRecPtr	fc_start_lsn;
	XLogRecPtr	fc_end_lsn = InvalidXLogRecPtr;
	bool		fc_stats_per_record;

	fc_start_lsn = PG_GETARG_LSN(0);
	fc_stats_per_record = PG_GETARG_BOOL(1);

	fc_end_lsn = fc_ValidateInputLSNs(true, fc_start_lsn, fc_end_lsn);

	fc_GetWalStats(fcinfo, fc_start_lsn, fc_end_lsn, fc_stats_per_record);

	PG_RETURN_VOID();
}
